﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using blackhouse.BlackApart.Business.Apartments;

namespace blackhouse.BlackApart.Business.Reservations {
    public class ConfiguredUserReservation : IUserReservation {

        #region Properties

        public int Id { get; set; }
        public DateTime StartDate { get; private set; }
        public DateTime EndDate { get; private set; }
        public int NumberOfPeoples { get; private set; }
        public int GroupId { get; private set; }

        #endregion

        #region IUserReservation Members

        public void SetDates(DateTime startDate, DateTime endDate) {
            if (startDate.Date < DateTime.Now.Date) throw new ExceptionAparts(2, null, new KeyValuePair<string, object>("startDate", startDate));
            if (startDate.Date >= endDate) throw new ExceptionAparts(3, null, new KeyValuePair<string, object>("startDate", startDate), new KeyValuePair<string, object>("endDate", endDate));
            this.StartDate = startDate.Date.AddHours(14);
            this.EndDate = endDate.Date.AddHours(12);
        }

        public void SetForPeoples(int count) {
            if (count <= 0 || count > 10000) throw new ExceptionAparts(4, null, new KeyValuePair<string, object>("numberOfPeople", count));
            this.NumberOfPeoples = count;
        }

        public Variant[] Find() {
            return this.Find(0);
        }

        public Variant[] Find(int apartmentGroupId) {
            this.Id = 1;
            IApartment baseApartment = Factory.Instance().GetApartment();
            IApartment[] apartments;
            if (apartmentGroupId == 0)
                apartments = baseApartment.GetAllFreeApartments(this.StartDate, this.EndDate);
            else
                apartments = baseApartment.GetAllFreeApartments(this.StartDate, this.EndDate, apartmentGroupId);

            List<Variant> variants = new List<Variant>();
            foreach (IApartmentGroup group in apartments.Select(a => a.Group).Distinct()) {
                if (this.GroupId != 0 && this.GroupId != group.Id) continue;
                Variant v = this.CombineReservationForGroup(apartments.Where(a => a.Group.Equals(group)).ToList(), this.NumberOfPeoples, this.StartDate, this.EndDate);
                if (v.Items.Length > 0)
                    variants.Add(v);
            }
            return variants.ToArray();
        }

        #endregion

        #region Methods

        //private ApartmentUserReservation[] CombineReservation(List<IApartment> apartments, int numberOfPeoples) {

        //    if (apartments.Count() == 0) return new ApartmentUserReservation[0];
        //    if (apartments.Count() == 1) {
        //        if (apartments[0].NumberOfPeople >= numberOfPeoples)
        //            return new ApartmentUserReservation[] { new ApartmentUserReservation() { Apartment = apartments[0], NumberOfPeople = numberOfPeoples } };
        //        else
        //            return new ApartmentUserReservation[0];
        //    }
        //    int[] apartmentsSize = apartments.Select(a => a.NumberOfPeople).ToArray();
        //    if (apartmentsSize.Contains(numberOfPeoples)) {
        //        ApartmentUserReservation res = new ApartmentUserReservation();
        //        IApartment ap = apartments.FirstOrDefault(a => a.NumberOfPeople == numberOfPeoples);
        //        res.Apartment = ap;
        //        res.NumberOfPeople = numberOfPeoples;
        //        return new ApartmentUserReservation[] { res };
        //    }

        //    for (int xa = 2; xa < apartmentsSize.Length; xa++) {

        //        List<CombineReservationInnerHelper> reservations = new List<CombineReservationInnerHelper>();

        //        ArrayCombinations ac = new ArrayCombinations(apartmentsSize.Length, xa);
        //        while (ac.Next()) {
        //            CombineReservationInnerHelper cr = new CombineReservationInnerHelper();
        //            cr.ApartmentsSize = new int[xa];
        //            for (int xcr = 0; xcr < ac.Combination.Length; xcr++) {
        //                cr.ApartmentsSize[xcr] = apartmentsSize[ac.Combination[xcr]];
        //            }
        //            cr.Sum = cr.ApartmentsSize.Sum();
        //            if (cr.Sum >= numberOfPeoples) {
        //                cr.Avg = CalculateReservationAvg(cr.ApartmentsSize);
        //                reservations.Add(cr);
        //            }
        //        }
        //        if (reservations.Count == 0) continue;
        //        List<CombineReservationInnerHelper> reservations2 = reservations.Where(r => r.Sum == numberOfPeoples).ToList();
        //        CombineReservationInnerHelper goodFind = null;
        //        if (reservations2.Count == 1) goodFind = reservations2[0];
        //        if (reservations2.Count > 1) goodFind = reservations2.OrderBy(cr => cr.Avg).First();
        //        if (goodFind == null)
        //            goodFind = reservations.OrderBy(r => r.Sum).ThenBy(r => r.Avg).First();

        //        List<ApartmentUserReservation> apres = new List<ApartmentUserReservation>(goodFind.ApartmentsSize.Length);
        //        for (int xaa = 0; xa < goodFind.ApartmentsSize.Length; xaa++) {
        //            IApartment apart = apartments.FirstOrDefault(a => a.NumberOfPeople == goodFind.ApartmentsSize[xaa] && !apres.Any(aur => aur.Apartment.Id != a.Id));
        //            apres[xaa] = new ApartmentUserReservation() { Apartment = apart, NumberOfPeople = apart.NumberOfPeople };
        //        }
        //        return apres.ToArray();

        //    }
        //    return new ApartmentUserReservation[0];
        //}

        private Variant CombineReservationForGroup(List<IApartment> apartmentWithGroup, int numberOfPeoples, DateTime arrivalDate, DateTime departureDate) {
            Variant v = new Variant();
            if (apartmentWithGroup.Count() == 0) return v;
            if (apartmentWithGroup.Count() == 1) {
                if (apartmentWithGroup[0].NumberOfPeople >= numberOfPeoples)
                    v.AddItem(apartmentWithGroup[0], arrivalDate, departureDate, numberOfPeoples);
                return v;
            }
            int[] apartmentsSize = apartmentWithGroup.Select(a => a.NumberOfPeople).ToArray();
            if (apartmentsSize.Contains(numberOfPeoples)) {
                IApartment ap = apartmentWithGroup.FirstOrDefault(a => a.NumberOfPeople == numberOfPeoples);
                v.AddItem(ap, arrivalDate, departureDate, numberOfPeoples);
                return v;
            }
            if (apartmentsSize.Any(s => s > numberOfPeoples)) {
                int rApSize = 0;
                foreach (int aps in apartmentsSize.OrderBy(s => s)) {
                    if (aps > numberOfPeoples) {
                        rApSize = aps;
                        break;
                    }
                }
                IApartment ap = apartmentWithGroup.FirstOrDefault(a => a.NumberOfPeople == rApSize);
                v.AddItem(ap, arrivalDate, departureDate, numberOfPeoples);
                return v;
            }

            for (int xa = 2; xa < apartmentsSize.Length; xa++) {

                List<CombineReservationInnerHelper> reservations = new List<CombineReservationInnerHelper>();

                ArrayCombinations ac = new ArrayCombinations(apartmentsSize.Length, xa);
                while (ac.Next()) {
                    CombineReservationInnerHelper cr = new CombineReservationInnerHelper();
                    cr.ApartmentsSize = new int[xa];
                    for (int xcr = 0; xcr < ac.Combination.Length; xcr++) {
                        cr.ApartmentsSize[xcr] = apartmentsSize[ac.Combination[xcr]];
                    }
                    cr.Sum = cr.ApartmentsSize.Sum();
                    if (cr.Sum >= numberOfPeoples) {
                        cr.Avg = CalculateReservationAvg(cr.ApartmentsSize);
                        reservations.Add(cr);
                    }
                }
                if (reservations.Count == 0) continue;
                List<CombineReservationInnerHelper> reservations2 = reservations.Where(r => r.Sum == numberOfPeoples).ToList();
                CombineReservationInnerHelper goodFind = null;
                if (reservations2.Count == 1) goodFind = reservations2[0];
                if (reservations2.Count > 1) goodFind = reservations2.OrderBy(cr => cr.Avg).First();
                if (goodFind == null)
                    goodFind = reservations.OrderBy(r => r.Sum).ThenBy(r => r.Avg).First();

                ApartmentUserReservation[] apres = new ApartmentUserReservation[goodFind.ApartmentsSize.Length];
                for (int xaa = 0; xaa < goodFind.ApartmentsSize.Length; xaa++) {
                    IApartment apart = apartmentWithGroup.FirstOrDefault(a => a.NumberOfPeople == goodFind.ApartmentsSize[xaa] && !apres.Where(aur => aur.Apartment != null).Any(aur => aur.Apartment.Id == a.Id));
                    apres[xaa] = new ApartmentUserReservation() { Apartment = apart, NumberOfPeople = apart.NumberOfPeople };
                }
                foreach (ApartmentUserReservation apre in apres) {
                    if (apre.NumberOfPeople < numberOfPeoples) {
                        v.AddItem(apre.Apartment, arrivalDate, departureDate, apre.NumberOfPeople);
                        numberOfPeoples -= apre.NumberOfPeople;
                    }
                    else {
                        if (numberOfPeoples > 0) {
                            v.AddItem(apre.Apartment, arrivalDate, departureDate, numberOfPeoples);
                        }
                    }
                }
                return v;

            }
            return v;
        }

        private double CalculateReservationAvg(int[] ar) {
            double avg = ar.Average();
            double diference = 0;
            for (int arl = 0; arl < ar.Length; arl++) {
                diference += Math.Abs(ar[arl] - avg);
            }
            return diference;
        }

        public void SetGroupFilter(int groupId) {
            this.GroupId = groupId;
        }

        public void SetGroupFilter(IApartmentGroup group) {
            this.GroupId = group.Id;
        }

        #endregion

        #region Inner Struct

        private class CombineReservationInnerHelper {
            public int Sum;
            public double Avg;
            public int[] ApartmentsSize;
        }

        #endregion

    }
}
